/*
 * GPUC
 * 
 * Copyright © 2008 Attila T. Áfra <attila.afra@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 */

#include <GPUC/Copy.h>
#include "BufferPrivate.h"
#include "StreamPrivate.h"

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_copyBufferToMemory(GPUC_Buffer  source,
                        void        *destination)
{
    if (source->mapped)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return;
    }

    glBindBuffer(source->pboTarget, source->pboId);
    glGetBufferSubData(source->pboTarget, 0, (GLsizeiptr)source->size, destination);
    glBindBuffer(source->pboTarget, 0);

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_partialCopyBufferToMemory(GPUC_Buffer  source,
                               void        *destination,
                               size_t       sourceOffset,
                               size_t       length)
{
    if ((sourceOffset + length) > source->size)
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    if (source->mapped)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return;
    }

    glBindBuffer(source->pboTarget, source->pboId);
    glGetBufferSubData(source->pboTarget, (GLsizeiptr)sourceOffset, (GLsizeiptr)length,
                       destination);
    glBindBuffer(source->pboTarget, 0);

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_copyMemoryToBuffer(const void  *source,
                        GPUC_Buffer  destination)
{
    if (destination->mapped)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return;
    }

    glBindBuffer(destination->pboTarget, destination->pboId);
    glBufferSubData(destination->pboTarget, 0, (GLsizeiptr)destination->size, source);
    glBindBuffer(destination->pboTarget, 0);

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_partialCopyMemoryToBuffer(const void  *source, 
                               GPUC_Buffer  destination,
                               size_t       destinationOffset,
                               size_t       length)
{
    if ((destinationOffset + length) > destination->size)
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    if (destination->mapped)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return;
    }

    glBindBuffer(destination->pboTarget, destination->pboId);
    glBufferSubData(destination->pboTarget, (GLsizeiptr)destinationOffset, (GLsizeiptr)length,
                    source);
    glBindBuffer(destination->pboTarget, 0);

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_copyStreamToMemory(GPUC_Stream  source,
                        void        *destination)
{
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_RECTANGLE_ARB, source->textureId, 0);
    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);

    if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
    {
        glReadPixels(0, 0, (GLsizei)source->width, (GLsizei)source->height, source->textureFormat,
                     source->textureType, destination);

        GPUC_error = GPUC_Error_None;
    }
    else
    {
        GPUC_error = GPUC_Error_UnsupportedStreamType;
    }

    glReadBuffer(GL_NONE);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_RECTANGLE_ARB, 0, 0);
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_partialCopyStreamToMemory(GPUC_Stream           source,
                               void                 *destination,
                               const GPUC_Rectangle *sourceRect)
{
    if ((sourceRect->x < 0) ||
        (sourceRect->y < 0) ||
        ((sourceRect->x + sourceRect->width) > source->width) ||
        ((sourceRect->y + sourceRect->height) > source->height))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_RECTANGLE_ARB, source->textureId, 0);
    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);

    if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
    {
        glReadPixels((GLint)sourceRect->x, (GLint)sourceRect->y,
                     (GLsizei)sourceRect->width, (GLsizei)sourceRect->height,
                     source->textureFormat, source->textureType, destination);  

        GPUC_error = GPUC_Error_None;
    }
    else
    {
        GPUC_error = GPUC_Error_UnsupportedStreamType;
    }

    glReadBuffer(GL_NONE);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_RECTANGLE_ARB, 0, 0);
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_copyMemoryToStream(const void  *source,
                        GPUC_Stream  destination)
{
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, destination->textureId);
    glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, (GLsizei)destination->width,
                    (GLsizei)destination->height, destination->textureFormat,
                    destination->textureType, source);

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_partialCopyMemoryToStream(const void           *source,
                               GPUC_Stream           destination,
                               const GPUC_Rectangle *destinationRect)
{
    if ((destinationRect->x < 0) ||
        (destinationRect->y < 0) ||
        ((destinationRect->x + destinationRect->width) > destination->width) ||
        ((destinationRect->y + destinationRect->height) > destination->height))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, destination->textureId);
    glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
                    (GLint)destinationRect->x, (GLint)destinationRect->y,
                    (GLsizei)destinationRect->width, (GLsizei)destinationRect->height,
                    destination->textureFormat, destination->textureType, source);

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_copyStreamToBuffer(GPUC_Stream source,
                        GPUC_Buffer destination)
{
    if (source->size != destination->size)
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_RECTANGLE_ARB, source->textureId, 0);
    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);

    if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
    {
        glBindBuffer(GL_PIXEL_PACK_BUFFER, destination->pboId);
        glReadPixels(0, 0, (GLsizei)source->width, (GLsizei)source->height, source->textureFormat,
                     source->textureType, GPUC_BUFFER_OFFSET(0));
        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

        GPUC_error = GPUC_Error_None;
    }
    else
    {
        GPUC_error = GPUC_Error_UnsupportedStreamType;
    }

    glReadBuffer(GL_NONE);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_RECTANGLE_ARB, 0, 0);
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_partialCopyStreamToBuffer(GPUC_Stream           source,
                               GPUC_Buffer           destination,
                               const GPUC_Rectangle *sourceRect,
                               size_t                destinationOffset)
{
    if ((sourceRect->x < 0) ||
        (sourceRect->y < 0) ||
        ((sourceRect->x + sourceRect->width) > source->width) ||
        ((sourceRect->y + sourceRect->height) > source->height) ||
        (sourceRect->width * sourceRect->height * source->elementSize + destinationOffset >
         destination->size))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_RECTANGLE_ARB, source->textureId, 0);
    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);

    if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
    {
        glBindBuffer(GL_PIXEL_PACK_BUFFER, destination->pboId);
        glReadPixels((GLint)sourceRect->x, (GLint)sourceRect->y,
                     (GLsizei)sourceRect->width, (GLsizei)sourceRect->height,
                     source->textureFormat, source->textureType,
                     GPUC_BUFFER_OFFSET(destinationOffset));
        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

        GPUC_error = GPUC_Error_None;
    }
    else
    {
        GPUC_error = GPUC_Error_UnsupportedStreamType;
    }

    glReadBuffer(GL_NONE);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_RECTANGLE_ARB, 0, 0);
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_copyBufferToStream(GPUC_Buffer source,
                        GPUC_Stream destination)
{
    if (source->size != destination->size)
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, destination->textureId);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, source->pboId);
    glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, (GLsizei)destination->width,
                    (GLsizei)destination->height, destination->textureFormat,
                    destination->textureType, GPUC_BUFFER_OFFSET(0));
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_partialCopyBufferToStream(GPUC_Buffer           source,
                               GPUC_Stream           destination,
                               size_t                sourceOffset,
                               const GPUC_Rectangle *destinationRect)
{
    if ((destinationRect->x < 0) ||
        (destinationRect->y < 0) ||
        ((destinationRect->x + destinationRect->width) > destination->width) ||
        ((destinationRect->y + destinationRect->height) > destination->height) ||
        ((destinationRect->width * destinationRect->height * destination->elementSize +
         sourceOffset) > source->size))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, destination->textureId);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, source->pboId);
    glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
                    (GLint)destinationRect->x, (GLint)destinationRect->y,
                    (GLsizei)destinationRect->width, (GLsizei)destinationRect->height,
                    destination->textureFormat, destination->textureType,
                    GPUC_BUFFER_OFFSET(sourceOffset));
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/
